import java.util.EmptyStackException;

/**
 * This is a testing framework. Use it extensively to verify that your code is working
 * properly.
 */
public class Tester {

	private static boolean testPassed = true;
	private static int testNum = 0;
	private static boolean exceptionCought = false;
	
	/**
	 * This entry function will test all classes created in this assignment.
	 * @param args command line arguments
	 */
	public static void main(String[] args) {
		
		// Each function here should test a different class.
		
		testValueToken();
		testPostfixCalculator();
		testExpTokenizer();
		testPeekableStackAsArray();
		testInfixCalculator();
		
		
		
		// Notifying the user that the code have passed all tests. 
		if (testPassed) {
			System.out.println("All " + testNum + " tests passed!");
		}
	}

	/**
	 * This utility function will count the number of times it was invoked. 
	 * In addition, if a test fails the function will print the error message.  
	 * @param exp The actual test condition
	 * @param msg An error message, will be printed to the screen in case the test fails.
	 */
	private static void test(boolean exp, String msg) {
		testNum++;
		
		if (!exp) {
			testPassed = false;
			System.out.println("Test " + testNum + " failed: "  + msg);
		}
	}
	
	/**
	 * Checks the ValueToken class.
	 */
	private static void testValueToken() {
		
		ValueToken t1 = new ValueToken(5.1);
		ValueToken t2 = new ValueToken(0.1);
		
		
		test(t1.getValue() == 5.1, "Value should be 5.1.");
		test(t1.toString().equals("5.1"), "Value toString should be 5.1.");
		test(t2.getValue() == 0.1, "Value should be 0.1.");
		test(t2.toString().equals("0.1"), "Value toString should be 0.1.");
		test(t1.equals(t1), "This should be 'True'");
		
		
		
	}
	/**
	 * Test infix Calculator
	 */
	private static void testInfixCalculator(){
		InfixCalculator pc = new InfixCalculator();
		pc.evaluate("1 + 2");
		test(pc.getCurrentResult() == 3.0 , "The result is:"+pc.getCurrentResult()+" But it should be 3.0");
		InfixCalculator pc2 = new InfixCalculator();
		// testing precedence 
		pc2.evaluate("2 + 2 * 2");
		test(pc2.getCurrentResult() == 6.0 , "The result is:"+pc2.getCurrentResult()+" But it should be 6.0");
		// testing brackets
		InfixCalculator pc3 = new InfixCalculator();
		pc3.evaluate("( 2 + 2 ) * 2");
		test(pc3.getCurrentResult() == 8.0 , "The result is:"+pc3.getCurrentResult()+" But it should be 8.0");
		//testing double brackets 
		InfixCalculator pc4 = new InfixCalculator();
		pc4.evaluate(" ( ( 2 + 2 ) ) * 2");
		test(pc4.getCurrentResult() == 8.0 , "The result is:"+pc4.getCurrentResult()+" But it should be 8.0");
		//test more complex expressions
		InfixCalculator pc5 = new InfixCalculator();
		pc5.evaluate(" ( ( 10 / ( 2 ^ 2 ) / 3.75 ) * ( 6 ) )");
		test(pc5.getCurrentResult() == 4.0 , "The result is:"+pc5.getCurrentResult()+" But it should be 4.0");

		InfixCalculator pc6 = new InfixCalculator();
		pc6.evaluate("( ( 10 / ( 2 ^ 2 ) / 3.75 ) * ( 6 ) ) ^ ( 0.5 * ( ( ( 10.5 - 6.5 ) ) ) )");
		test(pc6.getCurrentResult() == 16.0 , "The result is:"+pc6.getCurrentResult()+" But it should be 16.0");
		
		InfixCalculator pc7 = new InfixCalculator();
		pc7.evaluate("10 + 5 * 8 + 10 * 2 ^ 5");
		test(pc7.getCurrentResult() == 370.0 , "The result is:"+pc7.getCurrentResult()+" But it should be 370.0");
		
		InfixCalculator pc8 = new InfixCalculator();
		pc8.evaluate("( 10 ) + ( 5 ) * ( 8 ) + ( 10 ) * 2 ^ 5");
		test(pc8.getCurrentResult() == 370.0 , "The result is:"+pc8.getCurrentResult()+" But it should be 370.0");
		//only pran
		InfixCalculator pc9 = new InfixCalculator();
		pc9.evaluate("( 10 )");
		test(pc9.getCurrentResult() == 10.0 , "The result is:"+pc9.getCurrentResult()+" But it should be 10.0");
		// only number
		InfixCalculator pc10 = new InfixCalculator();
		pc10.evaluate("10");
		test(pc10.getCurrentResult() == 10.0 , "The result is:"+pc10.getCurrentResult()+" But it should be 10.0");
		//more complex precedence checks
		InfixCalculator pc11 = new InfixCalculator();
		pc11.evaluate("2 + 2 * 6 * 8 + 2 ^ 3");
		test(pc11.getCurrentResult() == 106.0 , "The result is:"+pc11.getCurrentResult()+" But it should be 106.0");
		
		InfixCalculator pc12 = new InfixCalculator();
		pc12.evaluate("( 2 + 2 ) * 6 * 8 + 2 ^ 3");
		test(pc12.getCurrentResult() == 200.0 , "The result is:"+pc12.getCurrentResult()+" But it should be 200.0");
		//expected math behavior
		
		InfixCalculator pc13 = new InfixCalculator();
		pc13.evaluate("1 * 1 * 1 * 1 * 1");
		test(pc13.getCurrentResult() == 1.0 , "The result is:"+pc13.getCurrentResult()+" But it should be 1.0");
		
		InfixCalculator pc14 = new InfixCalculator();
		pc14.evaluate("1 * 1 * 1 * 1 * 0");
		test(pc14.getCurrentResult() == 0.0 , "The result is:"+pc14.getCurrentResult()+" But it should be 0.0");
		
		InfixCalculator pc15 = new InfixCalculator();
		pc15.evaluate("5 ^ 0");
		test(pc15.getCurrentResult() == 1.0 , "The result is:"+pc15.getCurrentResult()+" But it should be 1.0");
		
		InfixCalculator pc16 = new InfixCalculator();
		pc16.evaluate("5 ^ 1");
		test(pc16.getCurrentResult() == 5.0 , "The result is:"+pc16.getCurrentResult()+" But it should be 5.0");
		
		InfixCalculator pc17 = new InfixCalculator();
		pc17.evaluate("7 * ( 5 ^ 1 - 5 ^ 1 )");
		test(pc17.getCurrentResult() == 0.0 , "The result is:"+pc17.getCurrentResult()+" But it should be 0.0");
		
		InfixCalculator pc18 = new InfixCalculator();
		pc18.evaluate("5 / 1");
		test(pc18.getCurrentResult() == 5.0 , "The result is:"+pc18.getCurrentResult()+" But it should be 5.0");
		
		InfixCalculator pc19 = new InfixCalculator();
		pc19.evaluate("1 ^ 5");
		test(pc19.getCurrentResult() == 1.0 , "The result is:"+pc19.getCurrentResult()+" But it should be 1.0");
		
		InfixCalculator pc20 = new InfixCalculator();
		pc20.evaluate("1 / 2 + 1 / 2");
		test(pc20.getCurrentResult() == 1.0 , "The result is:"+pc20.getCurrentResult()+" But it should be 1.0");
		
		InfixCalculator pc21 = new InfixCalculator();
		pc21.evaluate("1 / 2 + 0.5");
		test(pc21.getCurrentResult() == 1.0 , "The result is:"+pc21.getCurrentResult()+" But it should be 1.0");
		
		InfixCalculator pc22 = new InfixCalculator();
		pc22.evaluate("1 / 2 * 2");
		test(pc22.getCurrentResult() == 1.0 , "The result is:"+pc22.getCurrentResult()+" But it should be 1.0");
		
		InfixCalculator pc23 = new InfixCalculator();
		pc23.evaluate("4 ^ ( 1 / 2 )");
		test(pc23.getCurrentResult() == 2.0 , "The result is:"+pc23.getCurrentResult()+" But it should be 2.0");
		
		
		
		
		
	}
	
	/**
	 * Tests PeekableStackAsArray
	 */
	private static void testPeekableStackAsArray(){
		
		PeekableStackAsArray testStack = new PeekableStackAsArray();
		ExpTokenizer tokenizer = new ExpTokenizer("3 4 + 7");
		while (tokenizer.hasMoreTokens()){
			Object token = tokenizer.nextElement();
			testStack.push(token);
		}
		//Testing the peek function
		test((((ValueToken)testStack.peek(0)).getValue() == 7.0),"The result is :"+((ValueToken)testStack.peek(0)).getValue()+" This should be 7.0");
		//Testing to see what happens when we peek for a non existing item
		try{
			exceptionCought = false;
			testStack.peek(testStack.size + 1);
		}catch (EmptyStackException e){
			exceptionCought = true;
		}finally{
			if (exceptionCought = true){
				testNum++;
			}else{
				System.out.println("Peek test: Empty Stack Exception was expected , test failed");
			}
		}
		//Testing stack Contents
		test(testStack.stackContents().equals("7.0+4.03.0"),"The result is"+testStack.stackContents()+" but it should be \"7.0+4.03.0\"");
		Object poped = testStack.pop();
		//Testing pop
		test(poped.toString().equals("7.0"), "The result is " +poped.toString() + " This should be 7.0");
		//Testing if popping really removed the item from the stack
		test(testStack.stackContents().equals("+4.03.0"),"The result is "+testStack.stackContents()+" but it should be \"+4.03.0\"");
		//Testing to see what happens when we pop more times then the stack actual size
		
		try{
			exceptionCought = false;
			for (int i = testStack.size + 1; i >= 0 ; i--){
				testStack.pop();
			}
		}catch (EmptyStackException e){
			exceptionCought = true;
				
		}finally{
			if (exceptionCought = true){
				testNum++;
			}else{
				System.out.println("Pop test: Empty Stack Exception was expected , test failed");
			}
		}
	}
	
	/**
	 * Tests the ExpTokenizer class;
	 */
	private static void testExpTokenizer(){
		ExpTokenizer tokenizer = new ExpTokenizer("3 4 + 7");
		String test1 = "";
		while (tokenizer.hasMoreTokens()){
			Object token = tokenizer.nextElement();
			test1 = test1 + token.toString();
		}
		test(test1.equals("3.04.0+7.0"), "Result ="+test1+" The string should be \"3.04.0+7.0\" "); //test tokenizer
		
		ExpTokenizer tokenizer2 = new ExpTokenizer("3 4 + 7 +8..");
		String test2 = "";
		try{
			exceptionCought = false;
			while (tokenizer2.hasMoreTokens()){
				Object token = tokenizer2.nextElement();
				test2 = test2 + token.toString();
			} 
		}catch (ParseException e){
			exceptionCought = true;
			
		}finally{
			if (exceptionCought){
				testNum++;
			}else{
				System.out.println("Tokenizer test :Exception was expected due to incorrect syntax , test failed");
			}
		}
		
	}
	
	/**
	 * Checks the PostfixCalculator class.
	 */
	private static void testPostfixCalculator() {
		
		PostfixCalculator pc = new PostfixCalculator();
		pc.evaluate("1 2 +");
		test(pc.getCurrentResult() == 3.0 , "1 + 2 shoulbe be 3.0 ");		//Test addition
		pc.evaluate("0.5 2 +");
		test(pc.getCurrentResult() == 2.5 , "2 + 0.5 shoulbe be 2.5 ");		//test addition with not whole numbers
		pc.evaluate("2 4 -");
		test(pc.getCurrentResult() == -2.0 , "result = "+pc.getCurrentResult()+" but 2 - 4 shoulbe be -2 ");	//test subtraction
		pc.evaluate("2 4 *");
		test(pc.getCurrentResult() == 8.0 , "result = "+pc.getCurrentResult()+" but 2 * 4 shoulbe be 8 ");		//test multiplication
		pc.evaluate("2 4 /");
		test(pc.getCurrentResult() == 0.5 , "result = "+pc.getCurrentResult()+" but 2 / 4 shoulbe be 0.2 ");	//test devision
		pc.evaluate("2 4 ^");
		test(pc.getCurrentResult() == 16.0 , "result = "+pc.getCurrentResult()+" but 2 ^ 4  shoulbe be 16");	//test power
		pc.evaluate("2 4 / 8 *");
		test(pc.getCurrentResult() == 4.0 , "result = "+pc.getCurrentResult()+" but 2 / 4 * 8 shoulbe be 4");	//test more than two operands 
		pc.evaluate("2 4 + 8 * 6 -");
		test(pc.getCurrentResult() == 42.0 , "Your result is = "+pc.getCurrentResult()+", but ((2+4)*8)-6 shoulbe be The answer to Life Universe and Everything !");	//test more than two operands
		pc.evaluate("10.02 10 -");
		test(isCloseEnough(pc.getCurrentResult(), 0.02)   , "result = "+pc.getCurrentResult()+" 10.02-10 sould be -0.02");	//test more than two operands
		// only 1 number
		pc.evaluate("10");
		test(pc.getCurrentResult() == 10.0 , "Your result is = "+pc.getCurrentResult()+"10 is expected");
		// normal math behavior
		
		pc.evaluate("10 0 +");
		test(pc.getCurrentResult() == 10.0 , "Your result is = "+pc.getCurrentResult()+"10 is expected");
		
		pc.evaluate("10 1 *");
		test(pc.getCurrentResult() == 10.0 , "Your result is = "+pc.getCurrentResult()+"10 is expected");
		
		pc.evaluate("10 1 /");
		test(pc.getCurrentResult() == 10.0 , "Your result is = "+pc.getCurrentResult()+"10 is expected");
		
		pc.evaluate("10 1 ^");
		test(pc.getCurrentResult() == 10.0 , "Your result is = "+pc.getCurrentResult()+"10 is expected");
		
		pc.evaluate("10 0 ^");
		test(pc.getCurrentResult() == 1.0 , "Your result is = "+pc.getCurrentResult()+"1 is expected");
		
		
		
		
	}
	/**
	 * Private function that checks if two numbers are close to the extent of 4 numbers after the decimal point 
	 * @param computerSolution Number that we get from the calculator
	 * @param expectedSolution Number that we except to get.
	 * @return true, if the two numbers are close enough, false otherwise.
	 */
	private static boolean isCloseEnough (double computerSolution , double expectedSolution ){
		boolean ans = false;
//		double abs = Math.abs(expectedSolution - computerSolution);
		if (Math.abs(expectedSolution - computerSolution) <= 0.0001){
			ans = true;
		}
		return ans;
	}
}